package com.dycong.common.IO.socket.SSL;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Iterator;

/**
 * Created by dycong on 2017/3/30.
 */
public class SSLNewClient {
    private boolean ifHandshakeDone = false;
    private Selector selector;
    private SSLContext sslContext;
    private SSLEngine sslEngine;
    // TODO: 2017/3/31  
    private ByteBuffer appOut; // clear text buffer for out
    private ByteBuffer appIn; // clear text buffer for in
    private ByteBuffer netOut; // encrypted buffer for out
    private ByteBuffer netIn; // encrypted buffer for in

    private CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
    private CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();

    private static Path KEY_STORY = Paths.get("");
    private static Path TRUST_CER = Paths.get("");

    private void initSSLContext() {
        try {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(new FileInputStream(KEY_STORY.toFile()), "key password".toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(keyStore, "passowrd".toCharArray());

            KeyStore keyStore1 = KeyStore.getInstance("JKS");
            keyStore1.load(new FileInputStream(TRUST_CER.toFile()), "trust password".toCharArray());
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
            trustManagerFactory.init(keyStore1);

            SSLContext context = SSLContext.getInstance("SSL");
            context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
            sslContext = context;
        } catch (KeyStoreException e) {

        } catch (FileNotFoundException e) {

        } catch (NoSuchAlgorithmException e) {

        } catch (CertificateException e) {

        } catch (IOException e) {

        } catch (UnrecoverableKeyException e) {

        } catch (KeyManagementException e) {

        }
    }

    private void getSSLEngine() {
        SSLEngine engine = sslContext.createSSLEngine();
        engine.setUseClientMode(true);
        sslEngine = engine;
    }

    private void createBuffers() {
        SSLSession session = sslEngine.getSession();
        int appSize = session.getApplicationBufferSize();
        int pacSize = session.getPacketBufferSize();
        appIn = ByteBuffer.allocate(appSize);
        appOut = ByteBuffer.allocate(appSize);
        netIn = ByteBuffer.allocate(pacSize);
        netOut = ByteBuffer.allocate(pacSize);
    }

    private void getSocket() {
        try {
            selector = Selector.open();
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("127.0.0.1", 12121));
            socketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {

        }
    }

    private void start() {
        while (true) {
            try {
                int index = selector.select();
                if (index == 0) continue;
            } catch (IOException e) {

            }
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                it.remove();
                handel(key);
            }


        }
    }

    private void handel(SelectionKey key) {
        try {
            if (key.isConnectable()) {
                SocketChannel channel = (SocketChannel) key.channel();
                // TODO: 2017/3/31  handShake
                doHandShake(channel);
//                 channel.configureBlocking(false);            //handShake fail needn't this line and next;
//                 channel.register(selector,SelectionKey.OP_READ);
            } else if (key.isReadable()) {
                if (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    channel.read(netIn);
                    netIn.flip();
                    SSLEngineResult result = null;
                    while (netIn.hasRemaining()) {
                        result = sslEngine.unwrap(netIn, appIn);
                        System.out.println(decoder.decode(appIn));
                        appIn.flip();
                    }
                    doTask();
                    appIn.clear();
                    if (result.getStatus() == SSLEngineResult.Status.OK) {
                        netIn.clear();
                        System.out.println("recieved OK");
                    } else if (result.getStatus() == SSLEngineResult.Status.CLOSED) {
                        doSSLClose(key);
                    }
                }
            } else if (key.isWritable()) {
                //先wrap数据，再判断handShake情况,,,
                do {
                    SSLEngineResult result = sslEngine.wrap(appOut, netOut);
                } while (appOut.remaining() > 0);
                doTask();
                netOut.flip();
                appOut.clear();
                if (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    while (netOut.hasRemaining()) {
                        channel.write(netOut);
                    }
                    netOut.clear();
                }
            }
        } catch (IOException e) {

        }
    }

    private void doHandShake(SocketChannel channel) {
        try {
            while (!ifHandshakeDone) {
                sslEngine.beginHandshake();
                SSLEngineResult.HandshakeStatus status = sslEngine.getHandshakeStatus();
                switch (status) {
                    case FINISHED:
                        //the status become FINISHED only when the ssl handshake is finished
                        //but we still need to send data, so do nothing here
                        break;
                    case NEED_TASK:
                        //do the delegate task if there is some extra work such as checking the keystore during the handshake
                        // TODO: 2017/3/31
                        status = doTask();
                        break;
                    case NEED_UNWRAP:
                        //unwrap means unwrap the ssl packet to get ssl handshake information
                        // TODO: 2017/3/31
                        channel.read(netIn);
                        netIn.flip();
                        doUnWarp();
                        break;
                    case NEED_WRAP:
                        //wrap means wrap the app packet into an ssl packet to add ssl handshake information
                        // TODO: 2017/3/31
                        status = doWarp();
                        channel.write(netOut);
                        netOut.clear();
                        break;
                    case NOT_HANDSHAKING:
                        //now it is not in a handshake or say byebye status. here it means handshake is over and ready for ssl talk
                        channel.configureBlocking(false);
                        channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                        ifHandshakeDone = true;
                        break;
                }
            }
        } catch (SSLException e) {

        } catch (IOException e) {

        }
    }
    private void doSSLClose(SelectionKey key){
        SocketChannel channel=(SocketChannel)key.channel();
        key.cancel();
        try {
            // TODO: 2017/4/1 ???
            channel.configureBlocking(true);
        }catch (IOException e){

        }

    }

    private SSLEngineResult.HandshakeStatus doWarp() {
        SSLEngineResult.HandshakeStatus status = null;
        try {
            do {
                SSLEngineResult result = sslEngine.wrap(appOut, netOut);
            }
            while (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP && appOut.remaining() > 0);
        } catch (SSLException e) {

        }
        status = doTask();
        appOut.clear();
        netOut.flip();
        return status;
    }

    private SSLEngineResult.HandshakeStatus doUnWarp() {
        SSLEngineResult.HandshakeStatus status = null;
        try {
            do {
                SSLEngineResult result = sslEngine.unwrap(netIn, appIn);
                System.out.println(result);
            }
            while (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP && appIn.remaining() > 0);
            status = doTask();
            netIn.clear();
        } catch (SSLException e) {

        }
        return status;
    }

    private SSLEngineResult.HandshakeStatus doTask() {
        Runnable runnable = null;
        while ((runnable = sslEngine.getDelegatedTask()) != null) {
            runnable.run();
        }
        SSLEngineResult.HandshakeStatus status = sslEngine.getHandshakeStatus();
        if (status == SSLEngineResult.HandshakeStatus.NEED_TASK) {
            throw new RuntimeException("handShake shouldn't need addition task ");
        }
        return status;
    }


}
